package com.common.web.netty.socket;

import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.*;
import io.netty.handler.codec.http.*;
import io.netty.handler.codec.http.websocketx.*;
import io.netty.handler.timeout.IdleStateEvent;
import io.netty.util.CharsetUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.HashMap;
import java.util.Map;

/**
 * @author 杨超
 */

public class WebSocketHandler  extends ChannelInboundHandlerAdapter {

    /**
     * 保存已连接用户
     */
    public static Map<String,Channel> socketMap = new HashMap<>();

    private Logger logger = LoggerFactory.getLogger(WebSocketHandler.class);

    private String URI = "ws";

    private WebSocketServerHandshaker handshake;


    private  NettyHandleService nettyHandleService;



    /**
     * 连接异常   需要关闭相关资源
     * @param ctx
     * @param cause
     * @throws Exception
     */
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause){
        logger.info("连接异常,资源关闭!" + ctx.channel().id());

        cause.printStackTrace();
        close(ctx);
    }

    /**
     * 关闭连接
     * @param ctx
     */
    private void close(ChannelHandlerContext ctx){
        ctx.close();
        ctx.channel().close();
        for (String s : socketMap.keySet()) {
            if(socketMap.get(s).equals(ctx.channel())){
                socketMap.remove(s);
            }
        }
        System.out.println(socketMap);
    }

    /**
     * 活跃的通道  也可以当作用户连接上客户端进行使用
     * @param ctx
     * @throws Exception
     */
    @Override
    public void channelActive(ChannelHandlerContext ctx){
        nettyHandleService.exceptionCaughtHandle();;
        logger.info("与客户端建立连接，通道开启！"+ ctx.channel());
    }


    /**
     * 不活跃的通道  就说明用户失去连接
     * @param ctx
     * @throws Exception
     */
    @Override
    public void channelInactive(ChannelHandlerContext ctx){
        logger.info("与客户端断开连接,"+ctx.channel()+"通道关闭！");
        close(ctx);
    }

    /**
     * 这里只要完成 flush
     * @param ctx
     * @throws Exception
     */
    @Override
    public void channelReadComplete(ChannelHandlerContext ctx){
        ctx.flush();
    }


    /**
     * 这里是保持服务器与客户端长连接  进行心跳检测 避免连接断开
     * @param ctx
     * @param evt
     * @throws Exception
     */
    @Override
    public void userEventTriggered(ChannelHandlerContext ctx, Object evt){
        if(evt instanceof IdleStateEvent){
            IdleStateEvent stateEvent = (IdleStateEvent) evt;
            PingWebSocketFrame ping = new PingWebSocketFrame();
            switch (stateEvent.state()){
                //读空闲（服务器端）
                case READER_IDLE:
                    logger.info("【"+ctx.channel().remoteAddress()+"】读空闲（服务器端）");
                    ctx.writeAndFlush(ping);
                    break;
                //写空闲（客户端）
                case WRITER_IDLE:
                    logger.info("【"+ctx.channel().remoteAddress()+"】写空闲（客户端）");
                    ctx.writeAndFlush(ping);
                    break;
                case ALL_IDLE:
                    logger.info("【"+ctx.channel().remoteAddress()+"】读写空闲");
                    break;
            }
        }
    }

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg){
        if(msg instanceof FullHttpRequest){
            doHandlerHttpRequest(ctx,(FullHttpRequest) msg);
        }else if(msg instanceof WebSocketFrame){
            doHandlerWebSocketFrame(ctx,(WebSocketFrame) msg);
        }
    }

    /**
     * websocket消息处理
     * @param ctx
     * @param msg
     */
    private void doHandlerWebSocketFrame(ChannelHandlerContext ctx, WebSocketFrame msg) {
        //判断msg 是哪一种类型  分别做出不同的反应
        if(msg instanceof CloseWebSocketFrame){
            handshake.close(ctx.channel(), (CloseWebSocketFrame) msg);
            return ;
        }
        if(msg instanceof PingWebSocketFrame){
            PongWebSocketFrame pong = new PongWebSocketFrame(msg.content().retain());
            ctx.channel().writeAndFlush(pong);
            return ;
        }
        if(msg instanceof PongWebSocketFrame){
            PingWebSocketFrame ping = new PingWebSocketFrame(msg.content().retain());
            ctx.channel().writeAndFlush(ping);
            return ;
        }
        if(!(msg instanceof TextWebSocketFrame)){
            throw new UnsupportedOperationException("不支持二进制");
        }else{
            ctx.channel().writeAndFlush(new TextWebSocketFrame(((TextWebSocketFrame) msg).text()));
        }

    }

    /**
     * websocket第一次连接握手
     * @param ctx
     * @param msg
     */
    private void doHandlerHttpRequest(ChannelHandlerContext ctx, FullHttpRequest msg) {
        // http 解码失败
        if(!msg.decoderResult().isSuccess() || !("websocket".equals(msg.headers().get("Upgrade")))){
            sendHttpResponse(ctx, msg,new DefaultFullHttpResponse(HttpVersion.HTTP_1_1,HttpResponseStatus.BAD_REQUEST));
            return;
        }
        //可以获取msg的uri来判断参数
        String uri = msg.uri();
        //获取链接附带参数进行用户关联(非后台消息推送可判断)
        int i = uri.lastIndexOf("/");
        if (i == 0){
            return;
        }
        WebSocketServerHandshakerFactory factory = new WebSocketServerHandshakerFactory(
                "ws://"+msg.headers().get("Host")+"/"+URI+"",null,false
        );
        handshake = factory.newHandshaker(msg);
        if(handshake == null){
            WebSocketServerHandshakerFactory.sendUnsupportedVersionResponse(ctx.channel());
        }else{
            //进行连接
            handshake.handshake(ctx.channel(), msg);
        }
        String id = uri.substring(i + 1, uri.length());
        //加入链接数据集
        socketMap.put(id,ctx.channel());
    }

    private static void sendHttpResponse(ChannelHandlerContext ctx, FullHttpRequest req, DefaultFullHttpResponse res) {
        // 返回应答给客户端
        if (res.status().code() != 200) {
            ByteBuf buf = Unpooled.copiedBuffer(res.status().toString(), CharsetUtil.UTF_8);
            res.content().writeBytes(buf);
            buf.release();
        }
        // 如果是非Keep-Alive，关闭连接
        ChannelFuture f = ctx.channel().writeAndFlush(res);
        if (!HttpUtil.isKeepAlive(req) || res.status().code() != 200) {
            f.addListener(ChannelFutureListener.CLOSE);
        }
    }

    /**
     * 单独推送
     * @param id
     * @param msg
     */
    public void sendMsg(String id,String msg){
        Channel channel = socketMap.get(id);
        channel.writeAndFlush(new TextWebSocketFrame(msg));
    }

    /**
     * 消息群推送
     * @param msg
     */
    public void sendAllMsg(String msg){
        //读取所有客户并推送消息
        for (String s : socketMap.keySet()) {
            Channel channel = socketMap.get(s);
            channel.writeAndFlush(new TextWebSocketFrame(msg));
        }
    }

}
